using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Citrix.Cmp;
using System.Diagnostics;
using System.Windows.Input;

namespace SkinningSample.ViewModel
{
    /// <summary>
    /// This is the root object in the view-model layer.
    /// </summary>
    internal class ViewModelManager : INotifyPropertyChanged
    {
        /// <summary>
        /// A ref to the App instance.
        /// </summary>
        private readonly App _theApp;

        /// <summary>
        /// The currently active navigation implementation. We flip between different navigation
        /// implementations based on the layout we are currently using.
        /// </summary>
        private Navigation _navigation;

        /// <summary>
        /// Manages the different skins our app supports.
        /// </summary>
        private Skinning _skinning;

        /// <summary>
        /// This is the view-model object for the root page in our application.
        /// </summary>
        /// <remarks>
        /// Long lived or frequently used pages can persist their view-model objects as members
        /// in the ViewModelManager class as we have done here.
        /// </remarks>
        private Page1ViewModel _page1Vm;

        /// <summary>
        /// Initialisation ctor.
        /// </summary>
        /// <param name="app">The application object.</param>
        /// <param name="modelSource">The root model object.</param>
        /// <exception cref="ArgumentNullException">Thrown if any arg is null.</exception>
        public ViewModelManager(App app)
        {
            if (null == app)
            {
                throw new ArgumentNullException("app");
            }

            _theApp = app;
            _skinning = new Skinning(app);
            _page1Vm = new Page1ViewModel(this);
            _navigation = new Navigation(this, _page1Vm);

            this.DefaultBackCommand = new DelegateCommand(() =>
                {
                    _navigation.NavigateBack();
                });
            _theApp.CmpWpfApi.Input.ButtonPressed += new EventHandler<ButtonPressedArgs>(CmpInput_ButtonPressed);
        }

        /// <summary>
        /// Returns a refernce to the global app object.
        /// </summary>
        public App App
        {
            get { return _theApp; }
        }

        /// <summary>
        /// The default navigate back command. This will trigger navigation back to the previous
        /// page in the navigation history. Only use this default back command if your page can
        /// navigate back while in any state.
        /// </summary>
        public DelegateCommand DefaultBackCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// The active navigation implementation. We swap the navigation implementation based
        /// on the active layout.
        /// </summary>
        public Navigation Navigation
        {
            get { return _navigation; }
        }

        /// <summary>
        /// Navigates the app to the specified page.
        /// </summary>
        /// <param name="target">The page to navigate to.</param>
        public void NavigateToPage(IPage target)
        {
            AssertUiThread();
            _navigation.NavigateForward(target);
        }

        /// <summary>
        /// Checks whether the current thread is the UI thread and asserts if it isn't. This is
        /// used both as a debug check as well as a declarative statement to other devs that the
        /// current code block has to run in the UI thread. There's no graceful way to recover
        /// when this condition is violated and it indicates a dev bug rather than an expected
        /// operating conditions (hence the assert semantics). In a release build WPF will throw
        /// at some point if this condition ever did get violated.
        /// </summary>
        [Conditional("DEBUG")]
        public void AssertUiThread()
        {
            if (System.Threading.Thread.CurrentThread != _theApp.Dispatcher.Thread)
            {
                Debug.Assert(System.Threading.Thread.CurrentThread == _theApp.Dispatcher.Thread,
                    "Operation not running in the UI thread.");
            }
        }

        /// <summary>
        /// Returns the skin manager.
        /// </summary>
        public Skinning SkinManager
        {
            get { return _skinning; }
        }

        /// <summary>
        /// Dispatches the supplied action onto the main UI thread.
        /// </summary>
        /// <param name="action">The action to dispatch. Null actions are ignored.</param>
        public void UiDispatch(Action action)
        {
            if (null != action)
            {
                if (System.Threading.Thread.CurrentThread == _theApp.Dispatcher.Thread)
                {
                    action();
                }
                else
                {
                    _theApp.Dispatcher.BeginInvoke(action);
                }
            }
        }

        /// <summary>
        /// Event handler for the user pressing a physical button on the mobile device that we
        /// have redirected up to the server.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event args.</param>
        private void CmpInput_ButtonPressed(object sender, ButtonPressedArgs e)
        {
            if (e.Button == ButtonId.Back)
            {
                _navigation.NotifyButtonPressed(e.Button);
            }
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Fired whenever a public property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the property change event.
        /// </summary>
        /// <param name="propertyName">The name of the property that changed.</param>
        private void OnPropertyChanged(string propertyName)
        {
            NotifyHelper.VerifyProperty(propertyName, this);
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion // INotifyPropertyChanged Members
    }
}
